public class LazyOne {
    private static LazyOne instance;

    private LazyOne() {
    }

    //方式1：
    public static synchronized LazyOne getInstance1() {
        if (instance == null) {
            instance = new LazyOne();
        }
        return instance;
    }

    //方式2：
    public static LazyOne getInstance2() {
        synchronized (LazyOne.class) {
            if (instance == null) {
                instance = new LazyOne();
            }
            return instance;
        }
    }

    //方式3：
    public static LazyOne getInstance3() {
        if (instance == null) {
            synchronized (LazyOne.class) {
                try {
                    Thread.sleep(10); // 加这个代码，暴露问题
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (instance == null) {
                    instance = new LazyOne();
                }
            }
        }

        return instance;
    }
    /*
        注意：
        上述方式3中有指令重排问题
        ① mem = allocate(); 为单例对象分配内存空间
        ② instance = mem; 将 instance 指向分配的空间，但是空间还没有初始化
        ③ ctorSingleton(instance); 为单例对象调用构造器

        如果在创建的②③过程中又来了一个线程，此时 instance 已经不是 null 了，但是对象并没有初始化完成。
        如果另一个线程返回了这个还未初始化的对象，并且使用了它就会有问题

        从 JDK2 开始，分配空间、引用内存空间、调用构造器初始化会在线程的工作存储区一次性完成，然后复制到主存储区。但是需要 volatile 关键字来避免指令重排。
    */
}